#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sstream>
#include <signal.h>
#include <PopupUtils.hpp>

#define MIN(a,b) ((a) < (b)? (a) : (b))

using namespace std;
using namespace PopupUtils;

OSSigHandler PopupUtils::signal(int p_signum, OSSigHandler p_handler) {
  return ::signal(p_signum, p_handler);
}

size_t PopupUtils::strnlen(const char *p_string, size_t p_maxlen) {
  return ::strnlen(p_string, p_maxlen);
}

//==============================================================================
// Filesystem management
//==============================================================================

FileStream *PopupUtils::openFileStream(const string & p_path,
                                  const string & p_mode)
{
  return ::fopen(p_path.c_str(), p_mode.c_str());
}

int PopupUtils::closeFileStream(FileStream *p_fileStream)
{
  return ::fclose(p_fileStream);
}

size_t PopupUtils::fillStreamFromBuffer(FileStream *p_stream,
                                       const void *p_buffer,
                                       size_t p_size)
{
  return ::fwrite(p_buffer, 1, p_size, p_stream);
}

size_t PopupUtils::fillBufferFromStream(void *p_buffer,
                                       size_t p_size,
                                       FileStream *p_stream)
{
  return ::fread(p_buffer, 1, p_size, p_stream);
}

string PopupUtils::dirname(const string & p_path)
{
  string _res = ".";

  if (p_path.length() > 0)
  {
    size_t _end = p_path.find_last_not_of('/');
    if (_end == string::npos)
    {
      _res = "/";
    }
    else
    {
      _end = p_path.find_last_of('/', _end);
      _res = p_path.substr(0, _end);
    }
  }
  return _res;
}

string PopupUtils::basename(const string & p_path, bool p_stripExtension)
{
  string _res = "";

  if (p_path.length() > 0)
  {
    size_t _start = p_path.find_last_of('/');
    if (_start == string::npos)
    {
      _res = p_path;
    }
    else
    {
      _res = p_path.substr(_start+1);
    }
  }

  if (p_stripExtension) {
    size_t _end = _res.find_last_of('.');
    if (_end != string::npos)
    {
      _res = _res.substr(0, _end);
    }
  }
  return _res;
}

string PopupUtils::extension(const string & p_filename)
{
  string _res = "";

  if (p_filename.length() > 0)
  {
    size_t _start = p_filename.find_last_of('.');
    if (_start == string::npos)
    {
      _res = p_filename;
    }
    else
    {
      _res = p_filename.substr(_start+1);
    }
  }
  return _res;
}

string PopupUtils::shiftFile(const string & p_filename, int p_shiftValue)
{
  stringstream _s;
  string _filename = basename(p_filename, true);
  string _extension = extension(p_filename);

  _s << _filename << p_shiftValue;
  if (_extension.length() > 0) {
    _s << "." << _extension;
  }
  return _s.str();
}

enum FileEntity {
  ANY,
  REGULAR_FILE,
  DIRECTORY,
  SYMLINK,
  REGULAR_FILE_SYMLINK,
  DIRECTORY_SYMLINK
};

bool PopupUtils::fexists(const string & p_path)
{
  struct stat _stat;
  memset(&_stat, 0, sizeof(struct stat));
  return (stat(p_path.c_str(), &_stat) == 0);
}

size_t PopupUtils::getFileSize(const string & p_path)
{
  struct stat _stat;
  memset(&_stat, 0, sizeof(struct stat));
  if (stat(p_path.c_str(), &_stat) == 0) {
    return _stat.st_size;
  } else {
    return 0;
  }
}

bool PopupUtils::ftest(const string & p_path, enum FileType p_type)
{
  struct stat _stat;
  memset(&_stat, 0, sizeof(struct stat));
  if (stat(p_path.c_str(), &_stat) != 0) {
    return false;
  } else {
    switch(p_type) {
    case ANY:
      return true;
    case REGULAR_FILE:
      return S_ISREG(_stat.st_mode);
    case DIRECTORY:
      return S_ISDIR(_stat.st_mode);
    case SYMLINK:
      return S_ISLNK(_stat.st_mode);
    default:
      return false;
    }
  }
}

bool PopupUtils::mkdir(const string & p_path, int p_permissions)
{
  bool _rc = true;

  if (!ftest(p_path, DIRECTORY))
  {
    _rc = false;
    if (errno == ENOENT)
    {
      string _parentdir = PopupUtils::dirname(p_path);
      _rc = PopupUtils::mkdir(_parentdir, p_permissions);
      if (_rc == true)
      {
        _rc = (::mkdir(p_path.c_str(), (mode_t) p_permissions) == 0);
      }
    }
  }
  return _rc;
}

bool PopupUtils::randomString(char *p_buffer, size_t p_size)
{
  bool rc = false;

#if 0
  size_t nbytes = p_size;
  ssize_t nbread = 0;

  int fd = open("/dev/random", O_RDONLY);
  if (fd != -1) {
    while (nbytes > 0) {
      nbread = read(fd, p_buffer, nbytes);
      if (nbread == -1) {
        rc = false;
        break;
      } else {
        nbytes -= nbread;
        if (nbytes == 0) {
          rc = true;
          break;
        }
      }
    }
    close(fd);
  }
#endif

  size_t _index = 0;
  long int _val = 0;
  struct timeval _tv;

  memset(&_tv, 0, sizeof(struct timeval));

  if (gettimeofday(&_tv, NULL) == 0) {
    srand(_tv.tv_usec);
    for (_index = 0; _index < p_size; _index += sizeof(_val)) {
      _val = rand();
      memcpy(&p_buffer[_index], &_val,
             MIN(sizeof(_val), (p_size-_index)));
    }
    rc = true;
  }

  return rc;
}

bool PopupUtils::dumpFileToBuffer(void *p_buffer, size_t p_bufferSize,
                                 const string & p_filepath)
{
  size_t _offset = 0;
  ssize_t _nbcopied = 0;
  size_t _nbremaining = getFileSize(p_filepath);

  // Make sure buffer is big enough
  if (p_bufferSize < _nbremaining) {
    return false;
  }

  // Open file
  int _fd = open(p_filepath.c_str(), O_RDONLY);
  if (_fd == 0) {
    return false;
  }
  else
  {
    // Copy...
    while (_nbremaining > 0) {
      _nbcopied = read(_fd, &(((char*)p_buffer)[_offset]), _nbremaining);
      if (_nbcopied == -1) {
        return false;
      }
      _offset += _nbcopied;
      _nbremaining -= _nbcopied;
    }
    close(_fd);
  }
  return true;
}

bool PopupUtils::dumpBufferToFile(const string & p_filepath,
                                 const void *p_buffer, size_t p_bufferSize)
{
  mode_t _mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
  int _fd = open(p_filepath.c_str(), O_WRONLY|O_CREAT|O_TRUNC, _mode);
  if (_fd == -1) {
    fprintf(stderr, "Cannot open file %s : %s\n", p_filepath.c_str(), strerror(errno));
    return false;
  } else {
    size_t _offset = 0;
    while (p_bufferSize > 0) {
      ssize_t _nbwritten = write(_fd, &(((char*)p_buffer)[_offset]), p_bufferSize);
      if (_nbwritten == -1) {
        fprintf(stderr, "write() failed : %s\n", strerror(errno));
        return false;
      }
      _offset += _nbwritten;
      p_bufferSize -= _nbwritten;
    }
    close(_fd);
  }

  return true;
}
